package com.project.poetry.common.util;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.project.poetry.common.constant.ConstantValue;
import com.project.poetry.common.exception.ServiceException;
import com.project.poetry.service.entity.po.user.UserPO;
import org.springframework.util.DigestUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 工具类
 * @author mengxj
 */
public final class CommonUtil {

    private CommonUtil(){}

    /**
     * 将诗词拆分成诗词数组的符号依据
     */
    private static final String REGEX = "[。 ,！？.?!]";

    /**
     * 拆分诗词的正则对象
     */
    private static final Pattern PATTERN = Pattern.compile(REGEX);

    /**
     * 用来格式化时间
     */
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * 将字符串进行md5加密
     * @param str 入参字符串
     * @return 加密后字符串
     */
    public static String md5String(String str){
        return DigestUtils.md5DigestAsHex(str.getBytes());
    }

    /**
     * 获取当前人信息
     * @return 当前人信息
     */
    public static UserPO getUser(){
        return (UserPO) getRequest().getSession().getAttribute(ConstantValue.CUR_USER);
    }

    /**
     * 将当前登陆人存入session
     * @param user 当前登陆人
     */
    public static void saveUser(UserPO user){
        getRequest().getSession().setAttribute(ConstantValue.CUR_USER, user);
    }

    /**
     * 退出登录
     */
    public static void logout(){
        HttpSession session = getRequest().getSession();
        session.removeAttribute(ConstantValue.CUR_USER);
    }

    /**
     * 获取request对象
     * @return request对象
     */
    public static HttpServletRequest getRequest(){
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        assert requestAttributes != null;
        HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
        return Objects.requireNonNull(request);
    }

    /**
     * 根自定义逻辑和触发自定义逻辑主体的结果判断是否需要抛出异常
     * @see ServiceException
     * @param fun 自定义逻辑
     * @param t 触发自定义逻辑主体
     * @param message 异常信息
     * @param <T> 触发自定义逻辑主体类型
     * @see ConstantValue#DEFAULT_ERROR_STATUS_CODE
     */
    public static <T> void checkThrow(Function<T, Boolean> fun, T t, String message){
        checkThrow(fun, t, ConstantValue.DEFAULT_ERROR_STATUS_CODE, message);
    }

    /**
     * 根自定义逻辑和触发自定义逻辑主体的结果判断是否需要抛出异常
     * @see ServiceException
     * @param fun 自定义逻辑
     * @param t 触发自定义逻辑主体
     * @param code 异常状态码
     * @param message 异常信息
     * @param <T> 触发自定义逻辑主体类型
     */
    public static <T> void checkThrow(Function<T, Boolean> fun, T t, int code, String message){
        if(fun.apply(t)){
            throw new ServiceException(code, message);
        }
    }

    private static final String CHECK = "[\u4e00-\u9fa5]+";

    /**
     * 校验是不是中文
     * @param line 字符串
     * @return 结果
     */
    public static boolean checkChinese(String line){
        return !Pattern.matches(CHECK, line);
    }

    /**
     * 将诗词拆分成数组
     * @param poetry 诗词字符串
     * @param flag 断句处，只有词能用到这个参数，这和数字区分词的上下片
     *             非必传，最小值是0，0代表不分片
     * @return 诗词数组
     */
    public static String[] splitPoetryToArray(String poetry, Integer flag){
        Matcher matcher = PATTERN.matcher(poetry);
        String[] arr = poetry.split(REGEX);
        int row = arr.length;
        String[] chars = new String[row];
        int charIndex = 0;
        while (matcher.find()){
            String str = matcher.group().trim();
            chars[charIndex ++] = str;
        }
        boolean isZero = Objects.isNull(flag) || flag == 0;
        String[] strings = new String[row + (isZero ? 0 : 1)];
        for(int i = 0; i < row; i++){
            if(!isZero && i > flag - 1){
                if(i == flag){
                    strings[i] = "";
                }
                strings[i + 1] = arr[i] + chars[i];
            }else{
                strings[i] = arr[i] + chars[i];
            }
        }
        return strings;
    }

    /**
     * 将json字符串变成字符串，json中每一个元素用回车+换行（即新起一行）
     * 标识符替换，如：
     * json：
     *      ["hello", "world"]
     * 结果：
     *      hello
     *      world
     * @param jsonString json字符串
     * @return 结果字符串
     */
    public static String convertJsonToString(String jsonString){
        List<String> arr = JSON.parseArray(jsonString, String.class);
        return arr.stream().map(String :: trim).collect(Collectors.joining("\n"));
    }

    /**
     * 通用返回方法-不分页
     * @param viewName 视图名
     * @param t 返回数据
     * @param <T> 泛型
     * @return 返回模型视图
     */
    public static <T> ModelAndView success(String viewName, T t){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName(viewName);
        modelAndView.addObject("res", t);
        return modelAndView;
    }

    /**
     * 分页对象
     * @param pageNum 页码
     * @param pageSize 页面大小
     * @param <T> 分页结果类型
     * @return 分页对象
     */
    public static <T> Page<T> getPage(Integer pageNum, Integer pageSize){
        Function<Integer, Boolean> fun = Objects :: isNull;
        checkThrow(fun, pageNum, ConstantValue.PARAM_ERROR_STATUS_CODE, "分页查询页码不能为空");
        checkThrow(fun, pageSize, ConstantValue.PARAM_ERROR_STATUS_CODE, "分页查询页面大小不能为空");
        return new Page<>(pageNum, pageSize);
    }

    /**
     * mybatis-plus的相等条件
     * @param wrapper 查询对象
     * @param column 列名
     * @param value 值
     * @param <T> 查询对象的泛型类型，也就是查询返回结果的类型
     */
    public static <T> void eq(QueryWrapper<T> wrapper, String column, Object value){
        if(Objects.nonNull(value) && !StrUtil.EMPTY.equals(value.toString().trim())){
            wrapper.eq(column, value);
        }
    }

    /**
     * mybatis-plus的数字相等条件
     * @param wrapper 查询对象
     * @param column 列名
     * @param value 值
     * @param <T> 查询对象的泛型类型，也就是查询返回结果的类型
     */
    public static <T> void eqNumber(QueryWrapper<T> wrapper, String column, Number value){
        if(Objects.nonNull(value) && gtZero(value)){
            wrapper.eq(column, value);
        }
    }

    /**
     * mybatis-plus的模糊查询条件
     * @param wrapper 查询对象
     * @param column 列名
     * @param value 值
     * @param <T> 查询对象的泛型类型，也就是查询返回结果的类型
     */
    public static <T> void like(QueryWrapper<T> wrapper, String column, Object value){
        if(Objects.nonNull(value) && !StrUtil.EMPTY.equals(value.toString().trim())){
            wrapper.like(column, value);
        }
    }

    /**
     * 判断当前对象是否是数字，如果是数字再判断是否大于0，是则返回true，
     * 不是数字或不大于0都返回false
     * @param obj 判断对象
     * @return obj是否大于0
     */
    private static boolean gtZero(Object obj){
        try {
            return Double.parseDouble(obj.toString()) > 0;
        }catch (Exception e){
            return false;
        }
    }

    /**
     * 判断集合是空集合
     * @param list 待判断的集合
     * @param <T> 集合的泛型
     * @return 集合是否是空集合
     */
    public static <T> boolean isEmpty(List<T> list){
        return Objects.isNull(list) || list.isEmpty();
    }

    /**
     * 判断集合是非空集合
     * @param list 待判断的集合
     * @param <T> 集合的泛型
     * @return 集合是否是空集合
     */
    public static <T> boolean isNotEmpty(List<T> list){
        return !isEmpty(list);
    }

    /**
     * 获取当前时间
     * @return 当前时间
     */
    public static LocalDateTime nowDateTime(){
        return LocalDateTime.now();
    }

    /**
     * 获取形如yyyy-MM-dd HH:mm:ss的当前时间字符串，如2021-12-12 12:12:12
     * @return 当前时间字符串
     */
    public static String nowDateTimeString(){
        return DATE_TIME_FORMATTER.format(nowDateTime());
    }
}
